/*
 * Copyright 2008 the Breachwalls OSS project and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.breachwalls.mogen.generator.executor;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.breachwalls.mogen.generator.ModelGenerationException;
import com.breachwalls.mogen.generator.ModelGenerationProperties;
import com.breachwalls.mogen.generator.template.FreeMarkerDatatypeModelTemplateFactory;
import com.breachwalls.mogen.generator.template.FreeMarkerDomainModelTemplateFactory;
import com.breachwalls.mogen.generator.template.FreeMarkerEntityModelTemplateFactory;
import com.breachwalls.mogen.generator.template.FreeMarkerEnumModelTemplateFactory;
import com.breachwalls.mogen.generator.template.GenerateFile;
import com.breachwalls.mogen.generator.template.ModelTemplate;
import com.breachwalls.mogen.generator.template.ModelTemplateFactory;
import com.breachwalls.mogen.model.Model;
import com.breachwalls.mogen.model.ModelType;

import freemarker.cache.URLTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * FreeMarkerを使用して自動生成を行うエグゼクタ.
 * @author todoken
 * @version $Revision: 31 $
 */
public class FreeMarkerModelGenerationExecutor implements ModelGenerationExecutor {

    /**
     * モデルテンプレートのファクトリ.
     */
    private static Map<ModelType, ModelTemplateFactory> factories = new HashMap<ModelType, ModelTemplateFactory>();

    static {
        factories.put(ModelType.STRING, new FreeMarkerDatatypeModelTemplateFactory());
        factories.put(ModelType.DECIMAL, new FreeMarkerDatatypeModelTemplateFactory());
        factories.put(ModelType.DATETIME, new FreeMarkerDatatypeModelTemplateFactory());
        factories.put(ModelType.ENUM, new FreeMarkerEnumModelTemplateFactory());
        factories.put(ModelType.ENTITY, new FreeMarkerEntityModelTemplateFactory());
        factories.put(ModelType.DOMAIN, new FreeMarkerDomainModelTemplateFactory());
    }

    /**
     * FreeMarkerのコンフィグレーション.
     */
    private Configuration configuration = new Configuration();

    /**
     * モデル自動生成の外部設定.
     */
    private ModelGenerationProperties modelGenerationProperties;

    /**
     * コンストラクタ.
     * @param newModelGeneratorProperties モデル自動生成の外部設定
     */
    public FreeMarkerModelGenerationExecutor(ModelGenerationProperties newModelGeneratorProperties) {
        this.modelGenerationProperties = newModelGeneratorProperties;
        this.configuration.setOutputEncoding(modelGenerationProperties.getOutputEncoding());
        this.configuration.setTemplateLoader(new URLTemplateLoader() {
            @Override
            protected URL getURL(final String name) {
                String location = modelGenerationProperties.getTemplateLocation() + "/" + name;
                if (location.startsWith("/")) {
                    location = location.substring(1, location.length());
                }
                ClassLoader loader = getClass().getClassLoader();
                URL url = loader.getResource(location);
                return url;
            }
        });
        // TODO FreemarkerのExceptionHandlerとか色々必要かも
    }

    /**
     * モデルテンプレートファクトリを保存する.
     * @param type 登録したモデルテンプレートファクトリを使用するモデルタイプ
     * @param factory 登録するモデルテンプレートのファクトリ
     */
    public static synchronized void register(final ModelType type,
            final ModelTemplateFactory factory) {
        factories.put(type, factory);
    }

    /**
     * モデルテンプレートのファクトリからモデルテンプレートを作成する.
     * @param base 自動生成するファイルの出力先ベースディレクトリ
     * @param model 自動生成するモデルの情報
     * @return 自動生成するモデルが使用するモデルテンプレート
     */
    public synchronized ModelTemplate createModelTemplate(final File base, final Model model) {
        ModelType type = model.getModelType();
        ModelTemplateFactory factory = null;
        if (!factories.containsKey(type)) {
            // TODO 後でExceptionを変えること
            throw new RuntimeException("ModelTemplateFactory not found.");
        }
        factory = factories.get(type);
        return factory.createModelTemplate(base, model);
    }

    /**
     * {@inheritDoc}
     */
    public void execute(final Model model) throws ModelGenerationException {
        File base = modelGenerationProperties.getOutputDirectory();
        final ModelTemplate template = createModelTemplate(base, model);
        final Map<String, Object> param = new HashMap<String, Object>();
        param.put("model", model);
        param.put("template", template);

        try {
            Iterator<GenerateFile> files = template.iterateGenerateFiles();
            while (files.hasNext()) {
                GenerateFile file = files.next();
                Template t = configuration.getTemplate(file.getTemplateFileName());

                // 一度テンプレートを読み、全てのメソッドを実行する
                Writer writer = new StringWriter();
                t.process(param, writer);

                // ２回目は実際にファイルに書き出す
                writer = new FileWriter(file.getFile());
                t.process(param, writer);
                writer.flush();
            }
        } catch (IOException e) {
            throw new ModelGenerationException(e);
        } catch (TemplateException e) {
            throw new ModelGenerationException(e);
        }
    }

}
